package newcrunch.grader;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import newcrunch.util.PropertyLoader;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

/**
 *
 * @author ccadm
 */
public class ConnectionManager 
{
    private HttpClient httpClient;
    private boolean loggedIn;
    
    private HashMap<String, String> courseMapping = new HashMap<String, String>(); // course name -> course_id
    private HashMap<String, String> taskMapping = new HashMap<String, String>(); // task name -> tid
    private HashMap<String, String> nameMapping = new HashMap<String, String>(); // username -> first name
    private HashMap<String, String> groupMapping = new HashMap<String, String>(); // group name -> group_id
    
    private static final String baseUrl = PropertyLoader.getConnectionProperty("website");
    private static final String loginPage = PropertyLoader.getConnectionProperty("login");
    private static final String processPage = baseUrl + "/" + PropertyLoader.getConnectionProperty("process");
    
    private static final String ROW_DELIM = "\n";
    private static final String COL_DELIM = "@";
    private static final String TYPO_COL_DELIM = "\t";
    
    private static final String ERROR = "error:";
    
    private static final int READ_BUF = 1024;
    
    private ConnectionManager()
    {
        httpClient = new DefaultHttpClient();
    }
    
    public static ConnectionManager getConnection(String username, String password) throws 
            UnsupportedEncodingException, IOException
    {
        ConnectionManager mgr = new ConnectionManager();
        if (mgr.login(username, password))
            return mgr;
        else
            return null;
    }
    
    public HttpClient getHttpClient()
    {
        return httpClient;
    }
    
    public boolean login(String username, String password) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("username", username));
        params.add(new BasicNameValuePair("password", password));
        
        String reply = postForm(baseUrl + "/" + loginPage, params);
        
        if ("login success".equals(reply))
            return loggedIn = true;
        else
            return loggedIn = false;
    }
    
    public boolean isLoggedIn()
    {
        return loggedIn;
    }
    
    public String getName(String username) throws UnsupportedEncodingException, IOException
    {
        String result = null;
        if ((result = nameMapping.get(username)) != null)
            return result;
        
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_name"));
        params.add(new BasicNameValuePair("username", username));
        
        String name = postForm(params).trim();
        if (!"".equals(name))
            nameMapping.put(username, name);
        return name;
    }
    
    public String getUsername(String uid) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_username"));
        params.add(new BasicNameValuePair("uid", uid));
        
        return postForm(params).trim();
    }
    
    public List<String> getData(String type, String task) throws UnsupportedEncodingException, IOException
    {
        String tid = taskMapping.get(task);
        
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        
        if ("input".equals(type))
            params.add(new BasicNameValuePair("function", "get_input_data"));
        
        else if ("output".equals(type))
            params.add(new BasicNameValuePair("function", "get_output_data"));
        
        params.add(new BasicNameValuePair("tid", tid));
        
        HttpResponse response = postData(processPage, params);
        InputStream input = response.getEntity().getContent();
        ByteArrayOutputStream bOutStream = new ByteArrayOutputStream();
        
        byte[] buf = new byte[READ_BUF];
        
        int read = 0;
        while ((read = input.read(buf, 0, READ_BUF)) != -1)
            bOutStream.write(buf, 0, read);
        
        input.close();
        
        ZipInputStream zInput = new ZipInputStream(new ByteArrayInputStream(bOutStream.toByteArray()));
        List<String> data = extractTestCases(zInput);
        zInput.close();
        
        return data;
    }
    
    private List<String> extractTestCases(ZipInputStream zInput) throws IOException
    {
        ArrayList<String> result = new ArrayList<String>();
        
        ZipEntry entry = zInput.getNextEntry();
        while (entry != null)
        {
            ByteArrayOutputStream bArr = new ByteArrayOutputStream();
            byte[] buff = new byte[READ_BUF];
            int read = 0;
            while ((read = zInput.read(buff, 0, READ_BUF)) != -1)
            {
                bArr.write(buff, 0, read);
            }
            zInput.closeEntry();
            
            result.add(entry.getName());
            result.add(bArr.toString());
            entry = zInput.getNextEntry();
        }
                        
        return result;
    }
    
    public String getComment(String sid) throws UnsupportedEncodingException, IOException
    {
        //obtaining the comments separately
        List<NameValuePair> newParams = new ArrayList<NameValuePair>();
        newParams.add(new BasicNameValuePair("function", "get_comment"));
        newParams.add(new BasicNameValuePair("sid", sid));
        return postForm(newParams);
    }
    
    public List<SubmissionInfo> getSubmissionInfo(String course, String task, String group) throws UnsupportedEncodingException, IOException
    {
        String courseId = courseMapping.get(course);
        String tid = taskMapping.get(task);
        String groupId = groupMapping.get(group);
        
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_submission_info"));
        params.add(new BasicNameValuePair("course_id", courseId));
        params.add(new BasicNameValuePair("tid", tid));
        params.add(new BasicNameValuePair("group_id", groupId));
        
        List<SubmissionInfo> result = new ArrayList<SubmissionInfo>();
        String response = postForm(params);
        String[] rows = response.split(ROW_DELIM);
        
        for (String row : rows)
        {
            String[] field = row.split(COL_DELIM);
            if (field.length >= 8) 
            {
                SubmissionInfo info = new SubmissionInfo(field[0], field[1], field[2],
                        field[3], field[4], "", getName(getUsername(field[5])), field[6]);
                result.add(info);
            }
        }
        
        return result;
    }
    
    public String getMarkingScheme(String course, String task) throws UnsupportedEncodingException, IOException
    {
        String courseId = courseMapping.get(course);
        String tid = taskMapping.get(task);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_marking_scheme"));
        params.add(new BasicNameValuePair("course_id", courseId));
        params.add(new BasicNameValuePair("tid", tid));
        
        return postForm(params);
    }
    
    public String getSubmittedFile(String sid) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_submitted_file"));
        params.add(new BasicNameValuePair("sid", sid));
        return postForm(params);
    }
    
    public String getGradedFile(String sid) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_graded_file"));
        params.add(new BasicNameValuePair("sid", sid));
        return postForm(params);
    }
    
    public String[] getMarks(String sid) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_marks"));
        params.add(new BasicNameValuePair("sid", sid));
        return postForm(params).split(ROW_DELIM);
    }
    
    public int getCorrectCases(String sid) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_correct_cases"));
        params.add(new BasicNameValuePair("sid", sid));
        return Integer.parseInt(postForm(params));
    }
    
    public int getTotalCases(String sid) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_total_cases"));
        params.add(new BasicNameValuePair("sid", sid));
        return Integer.parseInt(postForm(params));
    }
    
    public String getCompilerOutput(String sid) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_compiler_output"));
        params.add(new BasicNameValuePair("sid", sid));
        
        return postForm(params);
    }
    
    public List<String> getStudents(String group) throws UnsupportedEncodingException, IOException
    {
        String groupId = groupMapping.get(group);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_students"));
        params.add(new BasicNameValuePair("group_id", groupId));
        
        String reply = postForm(params);
        
        String[] rows = reply.split(ROW_DELIM);
        List<String> result = new ArrayList<String>();
        for (String row : rows)
        {
            String[] fields = row.split(COL_DELIM);
            if (fields.length >= 3)
            {
                nameMapping.put(fields[0], fields[2]);
                result.add(fields[0]);
            }
        }
        return result;
    }
    
    public List<String> getTasks(String course) throws UnsupportedEncodingException, IOException
    {
        String courseId = courseMapping.get(course);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "list_tasks"));
        params.add(new BasicNameValuePair("course_id", courseId));
        
        String reply = postForm(params);
        
        String[] rows = reply.split(ROW_DELIM);
        List<String> result = new ArrayList<String>(rows.length);
        for (String row : rows)
        {
            int index = row.indexOf(COL_DELIM);
            String taskName = row.substring(index + 1);
            taskMapping.put(taskName, row.substring(0, index));
            result.add(taskName);
        }
        return result;
    }
    
    public List<String> getGroups(String course) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "get_groups"));
        params.add(new BasicNameValuePair("course_id", courseMapping.get(course)));
        
        String reply = postForm(params);
        
        String[] rows = reply.split(ROW_DELIM);
        List<String> result = new ArrayList<String>(rows.length);
        for (String row : rows)
        {
            String[] fields = row.split(COL_DELIM);
            if (fields.length > 1)
            {
                groupMapping.put(fields[0], fields[1]);
                result.add(fields[0]);
            }
        }
        
        return result;
    }    
    
    public List<String> getCourses() throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "list_courses"));
        
        String reply = postForm(params);
        
        String[] rows = reply.split(ROW_DELIM);
        List<String> result = new ArrayList<String>(rows.length);
        for (String row : rows)
        {
            String[] fields = row.split(COL_DELIM);
            if (fields.length > 1)
            {
                courseMapping.put(fields[1], fields[0]);
                result.add(fields[1]);
            }
        }
        
        return result;
    }
    
    public void sendGradingComment(String sid, String text) throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "send_comment"));
        params.add(new BasicNameValuePair("sid", sid));
        params.add(new BasicNameValuePair("comment", text));
        
        postForm(params);
    }
    
    public void sendGradedCode(String sid, String code, String filename) 
            throws UnsupportedEncodingException, IOException
    {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "send_code"));
        params.add(new BasicNameValuePair("sid", sid));
        params.add(new BasicNameValuePair("code", code));
        params.add(new BasicNameValuePair("filename", filename));
        
        postForm(params);
    }
    
    public void sendMarks(String sid, String[] marks) throws 
            UnsupportedEncodingException, IOException
    {
        if (marks == null || marks.length == 0)
            return;
        
        //converts the marks array into string
        StringBuilder builder = new StringBuilder();
        if (marks.length >= 1)
            builder.append(marks[0]);
        for (int i = 1; i < marks.length; i++)
            builder.append(ROW_DELIM + marks[i]);
        
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "send_marks"));
        params.add(new BasicNameValuePair("sid", sid));
        params.add(new BasicNameValuePair("marks", builder.toString()));
        
        postForm(params);
    }
    
    public void sendTypography(String sid, String[] result, String[] standardDeviation, String[] comments) 
            throws UnsupportedEncodingException, IOException
    {
        StringBuilder output = new StringBuilder();
        if (result == null || result.length == 0)
            return;
        
        output.append(result[0] + TYPO_COL_DELIM + standardDeviation[0] + TYPO_COL_DELIM + comments[0]);
        for (int i = 1; i < result.length; i++)
            output.append(ROW_DELIM + result[i] + TYPO_COL_DELIM + standardDeviation[i] + TYPO_COL_DELIM + comments[i]);
        
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("function", "send_typography"));
        params.add(new BasicNameValuePair("sid", sid));
        params.add(new BasicNameValuePair("content", output.toString()));
        
        postForm(params);
    }
    
    private String postForm(List<NameValuePair> params) throws UnsupportedEncodingException, IOException
    {
        return postForm(processPage, params);
    }
    
    private String postForm(String url, List<NameValuePair> params) throws UnsupportedEncodingException, IOException
    {
        HttpResponse response = postData(url, params);
        
        InputStream in = response.getEntity().getContent();
        
        StringBuilder output = new StringBuilder();
        int curr = -1;
        while ((curr = in.read()) != -1)
            output.append((char)curr);
        
        in.close();
        
        String ret = output.toString();
        
        if (ret.startsWith(ERROR))
            throw new IllegalArgumentException(ret.substring(ERROR.length()));
                
        return ret;
    }
    
    private HttpResponse postData(String url, List<NameValuePair> params) throws UnsupportedEncodingException, IOException
    {
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
        
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(entity);
        
        return httpClient.execute(httpPost);
    }
    
//    public static void main(String[] args) throws UnsupportedEncodingException, IOException
//    {
//        ConnectionManager mgr = ConnectionManager.getConnection("admin", "admin");
//        List<String> courses = mgr.getCourses();
//        for (String c : courses)
//        {
//            System.out.println(c + " :");
//                        
//            List<String> tasks = mgr.getTasks(c);
//            
//            
//            //submission info
//            mgr.getStudents(c);
//            for (String t : tasks)
//            {
//                List<SubmissionInfo> info = mgr.getSubmissionInfo(c, t);
//                for (SubmissionInfo i : info)
//                {
//                    System.out.println(i);
//                    System.out.println(mgr.getCompilerOutput(i.submissionId));
//                    System.out.println(mgr.getCorrectCases(i.submissionId) + 
//                            "/" + mgr.getTotalCases(i.submissionId));
//                    System.out.println(Arrays.toString(mgr.getMarks(i.submissionId)));
//                    System.out.println(mgr.getSubmittedFile(i.submissionId));
//                    System.out.println(mgr.getGradedFile(i.submissionId));
//                }
//            }
//            
//            //marking scheme
//            for (String t : tasks)
//            {
//                System.out.println(t);
//                System.out.println(mgr.getMarkingScheme(c, t));
//            }
//            
//            System.out.println("== students ==");
//            List<String> students = mgr.getStudents(c);
//            for (String s : students)
//                System.out.println(s);
//            
//            System.out.println("-----------------");
//        }
//    }
}
